import {Color} from '../../math/Color.js'
import {Path} from './Path.js'
import {Shape} from './Shape.js'
import {ShapeUtils} from '../ShapeUtils.js'

function ShapePath() {
  this.type = 'ShapePath'

  this.color = new Color()

  this.subPaths = []
  this.currentPath = null
}

Object.assign(ShapePath.prototype, {
  moveTo: function (x, y) {
    this.currentPath = new Path()
    this.subPaths.push(this.currentPath)
    this.currentPath.moveTo(x, y)

    return this
  },

  lineTo: function (x, y) {
    this.currentPath.lineTo(x, y)

    return this
  },

  quadraticCurveTo: function (aCPx, aCPy, aX, aY) {
    this.currentPath.quadraticCurveTo(aCPx, aCPy, aX, aY)

    return this
  },

  bezierCurveTo: function (aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
    this.currentPath.bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY)

    return this
  },

  splineThru: function (pts) {
    this.currentPath.splineThru(pts)

    return this
  },

  toShapes: function (isCCW, noHoles) {
    function toShapesNoHoles(inSubpaths) {
      const shapes = []

      for (let i = 0, l = inSubpaths.length; i < l; i++) {
        const tmpPath = inSubpaths[i]

        const tmpShape = new Shape()
        tmpShape.curves = tmpPath.curves

        shapes.push(tmpShape)
      }

      return shapes
    }

    function isPointInsidePolygon(inPt, inPolygon) {
      const polyLen = inPolygon.length

      // inPt on polygon contour => immediate success    or
      // toggling of inside/outside at every single! intersection point of an edge
      //  with the horizontal line through inPt, left of inPt
      //  not counting lowerY endpoints of edges and whole edges on that line
      let inside = false
      for (let p = polyLen - 1, q = 0; q < polyLen; p = q++) {
        let edgeLowPt = inPolygon[p]
        let edgeHighPt = inPolygon[q]

        let edgeDx = edgeHighPt.x - edgeLowPt.x
        let edgeDy = edgeHighPt.y - edgeLowPt.y

        if (Math.abs(edgeDy) > Number.EPSILON) {
          // not parallel
          if (edgeDy < 0) {
            edgeLowPt = inPolygon[q]
            edgeDx = -edgeDx
            edgeHighPt = inPolygon[p]
            edgeDy = -edgeDy
          }

          if (inPt.y < edgeLowPt.y || inPt.y > edgeHighPt.y) continue

          if (inPt.y === edgeLowPt.y) {
            if (inPt.x === edgeLowPt.x) return true // inPt is on contour ?
            // continue;				// no intersection or edgeLowPt => doesn't count !!!
          } else {
            const perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y)
            if (perpEdge === 0) return true // inPt is on contour ?
            if (perpEdge < 0) continue
            inside = !inside // true intersection left of inPt
          }
        } else {
          // parallel or collinear
          if (inPt.y !== edgeLowPt.y) continue // parallel
          // edge lies on the same horizontal line as inPt
          if ((edgeHighPt.x <= inPt.x && inPt.x <= edgeLowPt.x) || (edgeLowPt.x <= inPt.x && inPt.x <= edgeHighPt.x)) return true // inPt: Point on contour !
          // continue;
        }
      }

      return inside
    }

    const isClockWise = ShapeUtils.isClockWise

    const subPaths = this.subPaths
    if (subPaths.length === 0) return []

    if (noHoles === true) return toShapesNoHoles(subPaths)

    let solid, tmpPath, tmpShape
    const shapes = []

    if (subPaths.length === 1) {
      tmpPath = subPaths[0]
      tmpShape = new Shape()
      tmpShape.curves = tmpPath.curves
      shapes.push(tmpShape)
      return shapes
    }

    let holesFirst = !isClockWise(subPaths[0].getPoints())
    holesFirst = isCCW ? !holesFirst : holesFirst

    // console.log("Holes first", holesFirst);

    const betterShapeHoles = []
    const newShapes = []
    let newShapeHoles = []
    let mainIdx = 0
    let tmpPoints

    newShapes[mainIdx] = undefined
    newShapeHoles[mainIdx] = []

    for (let i = 0, l = subPaths.length; i < l; i++) {
      tmpPath = subPaths[i]
      tmpPoints = tmpPath.getPoints()
      solid = isClockWise(tmpPoints)
      solid = isCCW ? !solid : solid

      if (solid) {
        if (!holesFirst && newShapes[mainIdx]) mainIdx++

        newShapes[mainIdx] = {s: new Shape(), p: tmpPoints}
        newShapes[mainIdx].s.curves = tmpPath.curves

        if (holesFirst) mainIdx++
        newShapeHoles[mainIdx] = []

        //console.log('cw', i);
      } else {
        newShapeHoles[mainIdx].push({h: tmpPath, p: tmpPoints[0]})

        //console.log('ccw', i);
      }
    }

    // only Holes? -> probably all Shapes with wrong orientation
    if (!newShapes[0]) return toShapesNoHoles(subPaths)

    if (newShapes.length > 1) {
      let ambiguous = false
      const toChange = []

      for (let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {
        betterShapeHoles[sIdx] = []
      }

      for (let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {
        const sho = newShapeHoles[sIdx]

        for (let hIdx = 0; hIdx < sho.length; hIdx++) {
          const ho = sho[hIdx]
          let hole_unassigned = true

          for (let s2Idx = 0; s2Idx < newShapes.length; s2Idx++) {
            if (isPointInsidePolygon(ho.p, newShapes[s2Idx].p)) {
              if (sIdx !== s2Idx) toChange.push({froms: sIdx, tos: s2Idx, hole: hIdx})
              if (hole_unassigned) {
                hole_unassigned = false
                betterShapeHoles[s2Idx].push(ho)
              } else {
                ambiguous = true
              }
            }
          }

          if (hole_unassigned) {
            betterShapeHoles[sIdx].push(ho)
          }
        }
      }
      // console.log("ambiguous: ", ambiguous);

      if (toChange.length > 0) {
        // console.log("to change: ", toChange);
        if (!ambiguous) newShapeHoles = betterShapeHoles
      }
    }

    let tmpHoles

    for (let i = 0, il = newShapes.length; i < il; i++) {
      tmpShape = newShapes[i].s
      shapes.push(tmpShape)
      tmpHoles = newShapeHoles[i]

      for (let j = 0, jl = tmpHoles.length; j < jl; j++) {
        tmpShape.holes.push(tmpHoles[j].h)
      }
    }

    //console.log("shape", shapes);

    return shapes
  },
})

export {ShapePath}
